﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;

namespace Ebt.Mvvm.Behaviors
{
    /// <summary>
    /// Allows to automatically change a <see cref="FrameworkElement"/> style or visibility depending on the the current screen orientation.
    /// </summary>
    public static class OrientationBehavior
    {
        private static readonly HashSet<FrameworkElement> Elements;

        static OrientationBehavior()
        {
            Elements = new HashSet<FrameworkElement>();

            Initialize();
        }

        /// <summary>
        /// You should call this method in your <see cref="Application.OnLaunched"/> method or in a main UI thread when the main window is available.
        /// This is not required if you do not use this behavior in the Application XAML.
        /// </summary>
        public static void Initialize()
        {
            try
            {
                Window.Current.SizeChanged -= WindowOnSizeChanged;
                Window.Current.SizeChanged += WindowOnSizeChanged;
            }
            catch (Exception)
            {
            }
        }

        #region Attached properties

        #region Orientations

        public static readonly DependencyProperty OrientationsProperty =
            DependencyProperty.RegisterAttached("Orientations", typeof (WindowOrientations),
                                                typeof (OrientationBehavior),
                                                new PropertyMetadata(WindowOrientations.All, OnPropertyChanged));

        public static void SetOrientations(DependencyObject d, WindowOrientations value)
        {
            d.SetValue(OrientationsProperty, value);
        }

        public static WindowOrientations GetOrientations(DependencyObject d)
        {
            return (WindowOrientations) d.GetValue(OrientationsProperty);
        }

        #endregion

        #region LandscapeStyle

        public static readonly DependencyProperty LandscapeStyleProperty =
            DependencyProperty.RegisterAttached("LandscapeStyle", typeof(Style),
                                                typeof(OrientationBehavior),
                                                new PropertyMetadata(null, OnPropertyChanged));

        public static void SetLandscapeStyle(DependencyObject d, Style value)
        {
            d.SetValue(LandscapeStyleProperty, value);
        }

        public static Style GetLandscapeStyle(DependencyObject d)
        {
            return (Style)d.GetValue(LandscapeStyleProperty);
        }

        #endregion

        #region PortraitStyle

        public static readonly DependencyProperty PortraitStyleProperty =
            DependencyProperty.RegisterAttached("PortraitStyle", typeof(Style),
                                                typeof(OrientationBehavior),
                                                new PropertyMetadata(null, OnPropertyChanged));

        public static void SetPortraitStyle(DependencyObject d, Style value)
        {
            d.SetValue(PortraitStyleProperty, value);
        }

        public static Style GetPortraitStyle(DependencyObject d)
        {
            return (Style)d.GetValue(PortraitStyleProperty);
        }

        #endregion

        #region SnappedStyle

        public static readonly DependencyProperty SnappedStyleProperty =
            DependencyProperty.RegisterAttached("SnappedStyle", typeof(Style),
                                                typeof(OrientationBehavior),
                                                new PropertyMetadata(null, OnPropertyChanged));

        public static void SetSnappedStyle(DependencyObject d, Style value)
        {
            d.SetValue(SnappedStyleProperty, value);
        }

        public static Style GetSnappedStyle(DependencyObject d)
        {
            return (Style)d.GetValue(SnappedStyleProperty);
        }

        #endregion

        #region FilledStyle

        public static readonly DependencyProperty FilledStyleProperty =
            DependencyProperty.RegisterAttached("FilledStyle", typeof(Style),
                                                typeof(OrientationBehavior),
                                                new PropertyMetadata(null, OnPropertyChanged));

        public static void SetFilledStyle(DependencyObject d, Style value)
        {
            d.SetValue(FilledStyleProperty, value);
        }

        public static Style GetFilledStyle(DependencyObject d)
        {
            return (Style)d.GetValue(FilledStyleProperty);
        }

        #endregion


        #endregion
        
        private static void WindowOnSizeChanged(object sender, WindowSizeChangedEventArgs windowSizeChangedEventArgs)
        {
            Debug.WriteLine("Applying orientation to {0} elements.", Elements.Count);

            foreach (var element in Elements)
            {
                ApplyOrientation(element);
            }

            Debug.WriteLine("Applying orientation has been done.");
        }

        private static void ApplyOrientation(FrameworkElement element)
        {
            var orientations = GetOrientations(element);

            element.Visibility = orientations.HasFlag(OrientationsFromView(ApplicationView.Value))
                                 ? Visibility.Visible
                                 : Visibility.Collapsed;

            Style style;

            switch (ApplicationView.Value)
            {
                case ApplicationViewState.FullScreenPortrait:
                    style = GetPortraitStyle(element) ?? GetLandscapeStyle(element) ?? GetFilledStyle(element) ?? GetSnappedStyle(element);
                    break;
                case ApplicationViewState.Snapped:
                    style = GetSnappedStyle(element) ?? GetPortraitStyle(element) ?? GetFilledStyle(element) ?? GetLandscapeStyle(element);
                    break;
                case ApplicationViewState.Filled:
                    style = GetFilledStyle(element) ?? GetLandscapeStyle(element) ?? GetPortraitStyle(element) ?? GetSnappedStyle(element);
                    break;
                default:
                    style = GetLandscapeStyle(element) ?? GetFilledStyle(element) ?? GetPortraitStyle(element) ?? GetSnappedStyle(element);
                    break;
            }
            if (style != null)
            {
                element.Style = style;
            }
        }

        private static WindowOrientations OrientationsFromView(ApplicationViewState viewState)
        {
            switch (viewState)
            {
                case ApplicationViewState.FullScreenLandscape:
                    return WindowOrientations.Landscape;
                case ApplicationViewState.FullScreenPortrait:
                    return WindowOrientations.Portrait;
                case ApplicationViewState.Snapped:
                    return WindowOrientations.Snapped;
                case ApplicationViewState.Filled:
                    return WindowOrientations.Filled;
                default:
                    throw new NotSupportedException();
            }
        }

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = (FrameworkElement)d;

            element.Unloaded -= OnElementUnloaded;
            element.Loaded -= OnElementLoaded;

            element.Unloaded += OnElementUnloaded;
            element.Loaded += OnElementLoaded;

            Elements.Add(element);
            ApplyOrientation(element);
        }

        private static void OnElementUnloaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var element = sender as FrameworkElement;
            if (element != null)
            {
                Elements.Remove(element);
            }
        }

        private static void OnElementLoaded(object sender, RoutedEventArgs e)
        {
            var element = sender as FrameworkElement;
            if (element != null)
            {
                Elements.Add(element);
                ApplyOrientation(element);
            }
        }
    }
}
